5-3 函数类型
1. 函数基本定义与类型注解
1.1 声明式函数
通过function
关键字定义函数,参数和返回值都需要明确类型注解:
function add(arg1: number, arg2: number): number {
return arg1 + arg2;
}
typescript
核心要点
- 参数类型注解
- 直接写在参数名后(
arg1: number
) - 支持所有TypeScript类型(
string
,boolean
,object
, 自定义类型等) - 可选参数使用
?
标记:function greet(name: string, age?: number): string { return age ? `${name} is ${age} years old` : `Hello ${name}`; }
typescript
- 直接写在参数名后(
- 返回值类型注解
- 写在参数括号后(
: number
) - 无返回值时使用
void
:function logMessage(msg: string): void { console.log(msg); }
typescript
- 写在参数括号后(
- 调用规则
- 必须传递所有非可选参数
add(2, 3)
✅add(1)
❌ 参数不足 - 参数类型必须严格匹配
add(1, "text")
❌ 类型错误 - 支持默认参数:
function pow(base: number, exponent: number = 2): number { return base ** exponent; }
typescript
- 必须传递所有非可选参数
实践案例
// 计算商品总价
function calculateTotal(price: number, quantity: number, discount: number = 0): number {
return price * quantity * (1 - discount);
}
// 使用示例
const total = calculateTotal(100, 3); // 300
const discountedTotal = calculateTotal(100, 3, 0.2); // 240
typescript
1.2 类型校验特性
函数调用时会进行严格类型检查:
// 错误示例
const result = add(); // 编译错误:缺少参数
const result2 = add(1, "2"); // 编译错误:类型不匹配
typescript
深入理解
- 编译时检查
TypeScript的类型检查仅在编译阶段进行,不会影响运行时性能。例如:// 编译通过但运行时报错 const invalid: any = "text"; add(invalid, 1); // 运行时可能抛出错误
typescript - 严格模式差异
- 开启
strictNullChecks
时,null
/undefined
需要显式处理:function safeDivide(a: number, b: number | null): number { if (b === null) throw new Error("Divisor cannot be null"); return a / b; }
typescript
- 开启
- 类型推断机制
当返回值类型可明确推导时,可省略注解:function double(x: number) { // 自动推断返回number return x * 2; }
typescript
常见问题解答
Q:为什么参数类型不匹配会报错?
A:TypeScript通过静态类型检查确保代码安全性,避免运行时类型错误。
Q:如何允许灵活的参数类型?
A:使用联合类型:
function flexAdd(a: number | string, b: number | string): number {
return Number(a) + Number(b);
}
typescript
延伸学习
- TypeScript函数官方文档
- 练习:实现一个
formatDate
函数,支持Date
对象或时间戳输入,返回格式化字符串
💡提示:使用VS Code的TypeScript插件可以实时看到类型错误提示,大幅提升开发效率!
2. 箭头函数类型定义
2.1 基础箭头函数
核心语法
const 函数名 = (参数1: 类型, 参数2: 类型): 返回值类型 => {
// 函数体
return 返回值;
};
typescript
关键特性
- 与传统函数的等价性:
// 传统函数 function add(a: number, b: number): number { return a + b; } // 箭头函数 const add = (a: number, b: number): number => { return a + b; };
typescript - 类型注解位置:
- 参数类型:
(a: number, b: number)
- 返回值类型:
: number
(在参数括号后)
- 参数类型:
- this绑定差异:
- 箭头函数没有自己的
this
,会继承外层作用域的this
- 传统函数的
this
取决于调用方式
- 箭头函数没有自己的
实践案例
// 数组过滤示例
const numbers = [1, 2, 3, 4, 5];
const evens = numbers.filter((num: number): boolean => {
return num % 2 === 0;
});
typescript
常见错误
// 错误1:遗漏返回值类型
const add = (a: number, b: number) => a + b; // 可以但不推荐
// 错误2:错误使用this
const counter = {
count: 0,
increment: () => {
this.count++; // 错误!箭头函数的this指向外层
}
};
typescript
2.2 简写形式
语法规则
当满足以下条件时可使用简写:
- 函数体只有单条表达式
- 不需要
{}
包裹 - 自动返回表达式结果
// 完整形式
const double = (x: number): number => {
return x * 2;
};
// 简写形式
const double = (x: number): number => x * 2;
typescript
使用场景
- 简单的数学运算:
const square = (x: number): number => x * x;
typescript - 数组操作:
const names = ["Alice", "Bob"]; const lengths = names.map((name: string): number => name.length);
typescript - 条件表达式:
const isAdult = (age: number): boolean => age >= 18;
typescript
注意事项
- 多行逻辑仍需使用
{}
和return
- 返回对象字面量时需要加括号:
const getUser = (id: number): {id: number, name: string} => ({ id, name: "Anonymous" });
typescript
2.3 高级用法
泛型箭头函数
const identity = <T>(arg: T): T => arg;
typescript
异步箭头函数
const fetchData = async (url: string): Promise<Response> => {
return await fetch(url);
};
typescript
函数类型别名
type MathOperation = (a: number, b: number) => number;
const add: MathOperation = (x, y) => x + y;
typescript
对比总结
特性 | 传统函数 | 箭头函数 |
---|---|---|
语法 | function 关键字 | => 语法 |
this 绑定 | 动态绑定 | 静态绑定 |
构造函数 | 可用作构造函数 | 不能用作构造函数 |
简写形式 | 不支持 | 支持单行简写 |
参数类型 | 支持 | 支持 |
返回值类型 | 支持 | 支持 |
💡提示:在React组件中,箭头函数常用于避免this
绑定问题,特别是在事件处理函数中。
3. 函数类型变量声明
3.1 类型与实现分离
核心概念
TypeScript允许将函数类型声明与实现分离,这种方式特别适合以下场景:
- 需要先声明函数类型再延迟实现
- 函数实现可能变化但类型保持不变
- 需要明确约束函数的接口规范
详细语法解析
// 声明函数类型变量
let mathOperation: (x: number, y: number) => number;
// 后续实现
mathOperation = (num1, num2) => num1 + num2; // 实现加法
mathOperation = (base, exponent) => base ** exponent; // 改为实现幂运算
typescript
关键特性
- 类型安全保证:
- 所有赋值必须符合声明的函数类型
- 参数数量和类型必须严格匹配
- 返回值类型必须一致
- 参数名自由性:
type Comparator = (a: string, b: string) => number; // 参数名可以不同但类型必须匹配 const compare: Comparator = (str1, str2) => str1.length - str2.length;
typescript - 错误示例:
let operation: (n: number) => string; operation = (num: number) => num * 2; // 错误:返回值应该是string
typescript
实际应用场景
- 插件系统开发:
type Plugin = (config: object) => void; let initPlugin: Plugin; // 根据不同环境加载不同实现 if (env === 'production') { initPlugin = (cfg) => loadProdPlugin(cfg); } else { initPlugin = (cfg) => loadDevPlugin(cfg); }
typescript - 回调函数管理:
type EventHandler = (payload: EventPayload) => boolean; let currentHandler: EventHandler; function setHandler(handler: EventHandler) { currentHandler = handler; }
typescript
3.2 类型别名优化
类型别名的优势
- 提高代码可读性:
// 普通写法 const handlers: ((msg: string) => void)[] = []; // 使用类型别名 type MessageHandler = (msg: string) => void; const handlers: MessageHandler[] = [];
typescript - 便于统一修改:
type UserValidator = (user: User) => boolean; // 需要修改时只需改一处 type UserValidator = (user: User) => Promise<boolean>;
typescript
高级用法
- 联合函数类型:
type StringProcessor = | ((input: string) => string) | ((input: string[]) => string[]);
typescript - 泛型函数类型:
type Mapper<T, U> = (item: T) => U; const numberToString: Mapper<number, string> = (n) => n.toString();
typescript - 复杂函数签名:
type EventListener = { (type: 'click', handler: (e: MouseEvent) => void): void; (type: 'keypress', handler: (e: KeyboardEvent) => void): void; };
typescript
最佳实践建议
- 项目规范:
- 对重复使用的函数类型定义统一别名
- 将常用类型别名集中存放在
types.ts
文件中
- 命名约定:
- 使用
Handler
/Callback
/Processor
等后缀 - 泛型类型使用
T
/U
等约定俗成的类型参数
- 使用
- 文档注释:
/** * 用户认证函数类型 * @param credentials - 认证凭据 * @returns 认证令牌或null */ type AuthFunction = (credentials: Credentials) => string | null;
typescript
3.3 接口声明函数类型
与类型别名的对比
// 使用接口
interface MathOperation {
(x: number, y: number): number;
}
// 使用类型别名
type MathOperation = (x: number, y: number) => number;
typescript
接口的优势
- 可扩展性:
interface MathOperation { (x: number, y: number): number; description: string; // 可以添加属性 } const add: MathOperation = (x, y) => x + y; add.description = "加法运算";
typescript - 继承能力:
interface BinaryOperation extends MathOperation { operatorSymbol: string; }
typescript
使用场景建议
- 需要添加属性时使用接口
- 简单函数类型优先使用类型别名
- 需要组合类型时考虑联合类型或交叉类型
💡提示:TypeScript 4.2+支持在类型别名中使用模板字符串类型,可以创建更灵活的函数类型:
type EventName = `on${string}`;
type EventHandler = (event: Event) => void;
type EventHandlers = {
[K in EventName]?: EventHandler;
};
typescript
4. 类型推导机制
4.1 返回值类型推导
深入解析
TypeScript 的返回值类型推导是编译器最强大的功能之一,它通过以下逻辑工作:
- 显式注解优先:如果函数已声明返回值类型,则直接使用
- 表达式分析:对于未声明返回值的函数,分析所有 return 语句的表达式类型
- 联合类型合并:如果存在多个返回类型,自动合并为联合类型
// 示例1:基础推导
function double(x: number) {
return x * 2; // 推断返回number
}
// 示例2:多返回路径
function parseInput(input: string) {
if (input === "true") return true;
if (input === "false") return false;
return null; // 推断返回 boolean | null
}
typescript
高级场景
- 泛型函数推导:
function identity<T>(arg: T) { return arg; // 返回类型为T } const num = identity(123); // num类型为number
typescript - 异步函数推导:
async function fetchUser() { return await getUser(); // 推断返回Promise<User> }
typescript - 复杂对象推导:
function createConfig() { return { apiUrl: "https://api.example.com", timeout: 5000, retry: true }; // 精确推断返回类型 }
typescript
最佳实践
- 简单函数可依赖自动推导
- 复杂函数建议显式声明返回类型
- 使用
satisfies
运算符验证推导结果(TS 4.9+):const config = { apiUrl: "https://api.example.com" } satisfies { apiUrl: string };
typescript
4.2 上下文类型推导
工作原理
当变量已经具有明确的类型注解时,TypeScript 会提供"上下文类型"(contextual typing),这种机制在以下场景特别有用:
- 回调函数参数推导:
const numbers = [1, 2, 3]; numbers.map(num => num * 2); // num自动推断为number
typescript - 事件处理函数:
window.addEventListener("click", event => { console.log(event.clientX); // event被推断为MouseEvent });
typescript - React 组件 Props:
const Button = ({ onClick }: { onClick: (e: Event) => void }) => ( <button onClick={e => onClick(e)} /> // e自动推断为React.MouseEvent );
typescript
特殊场景处理
- 函数重载时的推导:
declare function createElement(tag: "div"): HTMLDivElement; declare function createElement(tag: "span"): HTMLSpanElement; const el = createElement("div"); // el精确推断为HTMLDivElement
typescript - 解构参数推导:
type User = { name: string; age: number }; const users: User[] = [{ name: "Alice", age: 30 }]; users.map(({ name }) => name.toUpperCase()); // name推断为string
typescript - 默认参数推导:
function createPoint(x = 0, y = 0) { return { x, y }; // 推断为{ x: number; y: number } }
typescript
调试技巧
当推导结果不符合预期时:
- 使用 VS Code 的悬停提示查看类型
- 添加临时类型断言验证:
const result = add(1, 2) as number;
typescript - 使用
// @ts-expect-error
注释测试错误情况
4.3 类型推导的限制
常见边界情况
- 循环引用推导:
let items = [{ name: "test", subItems: items }]; // 无法推导
typescript - 条件类型中的推导:
function conditional<T>(arg: T): T extends string ? number : boolean { return arg; // 需要显式类型断言 }
typescript - 函数参数默认值:
function merge(a = { x: 1 }, b = { y: 2 }) { return { ...a, ...b }; // a和b的类型可能过宽 }
typescript
强制类型提示
使用 @type
JSDoc 注释增强推导:
/** @type {(a: number, b: number) => number} */
const add = (a, b) => a + b;
typescript
💡提示:在 tsconfig.json 中配置 "noImplicitAny": true
可以强制要求所有类型必须明确,避免意外的 any
类型推导。
5. 函数重载预备知识
5.1 函数签名概念深度解析
函数签名的三大要素
- 函数名标识
- 命名需符合语义化原则
- 示例:
parseInput
比parse
更明确
- 参数类型列表
- 包含参数顺序和类型约束
- 支持复杂类型:
interface Coordinate { x: number; y: number; } function moveTo(pos: Coordinate): void;
typescript
- 返回值类型
- 明确函数输出契约
- 特殊类型示例:
function createLoader(): () => Promise<Data>;
typescript
签名与实现的区别
// 签名(类型层面)
type FetchSignature = (url: string, init?: RequestInit) => Promise<Response>;
// 实现(值层面)
const fetch: FetchSignature = async (url, init) => { /*...*/ };
typescript
实战练习
// 定义一个计算器签名
type Calculator = {
(a: number, b: number): number; // 基础运算
(a: string, b: string): string; // 字符串拼接
(values: number[]): number; // 数组求和
};
typescript
5.2 重载核心思想进阶
重载的三大核心原则
- 输入输出类型映射
function reverse(input: string): string; function reverse<T>(input: T[]): T[];
typescript - 参数组合模式匹配
function createElement(tag: 'img'): HTMLImageElement; function createElement(tag: 'input'): HTMLInputElement;
typescript - 类型安全保证
function padLeft(value: string, padding: number): string; function padLeft(value: string, padding: string): string; // 实现需校验padding类型
typescript
典型应用场景
场景 | 示例 |
---|---|
参数类型转换 | string 输入 → Date 输出 |
多态数据处理 | 处理`string |
API版本兼容 | 新旧参数格式支持 |
数学运算重载 | 向量/矩阵的不同运算模式 |
重载实现机制
5.3 重载预备练习
练习1:基础重载
// 声明重载
function makeDate(timestamp: number): Date;
function makeDate(y: number, m: number, d: number): Date;
// 实现
function makeDate(yOrTimestamp: number, m?: number, d?: number): Date {
return m === undefined ? new Date(yOrTimestamp) : new Date(yOrTimestamp, m, d);
}
typescript
练习2:类型守卫重载
function isStringArray(value: unknown): value is string[];
function isStringArray(value: unknown): boolean {
return Array.isArray(value) && value.every(item => typeof item === 'string');
}
typescript
练习3:异步重载
async function fetchData(id: number): Promise<User>;
async function fetchData(query: string): Promise<User[]>;
async function fetchData(param: number | string): Promise<User | User[]> {
// 实现逻辑
}
typescript
5.4 常见陷阱与解决方案
问题类型 | 错误示例 | 解决方案 |
---|---|---|
实现签名不兼容 | 实现参数比声明签名更宽泛 | 使用联合类型约束实现签名 |
返回值类型不一致 | 声明返回string 但实现返回number | 添加类型断言或修正逻辑 |
参数顺序错误 | 声明(a,b) 但实现(b,a) | 严格保持参数顺序一致 |
过度重载 | 10+个重载签名 | 考虑改用策略模式或工厂模式 |
5.5 扩展思考题
- 如何设计一个支持链式调用的重载函数?
- 在React组件prop类型设计中如何应用函数重载?
- 重载函数在DTO转换场景中的最佳实践是什么?
💡 下节课将深入讲解重载实现细节,建议提前尝试:
// 挑战题:实现一个智能clone函数
function clone<T>(value: T): T;
function clone<T>(value: T[], deep: boolean): T[];
function clone(value: any, deep?: boolean): any {
// 你的实现...
}
typescript
准备好你的TypeScript Playground,我们即将进入函数重载的奇妙世界! 🚀
↑